iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
Rust

Bevy Rogue-lite 勇者冒險篇 × Rust 遊戲開發筆記系列 第 28

打擊特效與動畫調整

  • 分享至 

  • xImage
  •  

繼續遊戲的調整,要做的是玩家出手時會有擊中閃光,敵人被打會閃爍,並且做出死亡粒子(Death Particle Effect)的效果,鏡頭也會跟著震動。所有效果都走事件 + Timer,讓動畫可以跟著 Bevy 的 ECS 節奏同步。

這回的重點:

  • EnemyHitEvent 成為攻擊系統與視覺效果的橋樑,集中傳遞傷害量、座標與剩餘血量。
  • 新增 HitSparkEnemyHitFlashDeathParticle 元件,利用 Timer 控制亮度/縮放/壽命。
  • CameraShake 資源負責震動參數,EffectsPluginCameraPlugin 串接鏡頭偏移。
  • 利用 rand 做死亡粒子扇形噴發,強化擊殺瞬間的回饋。

EnemyHitEvent 攻擊與特效的共通語言

new_demo/src/systems/enemy.rs:17 增加了 EnemyHitEvent,在玩家近戰命中敵人時會發出事件 (new_demo/src/systems/attack.rs:97):

hit_events.write(EnemyHitEvent {
    entity: enemy_entity,
    position: enemy_transform.translation,
    damage,
    remaining_health: health.current,
});

事件包含敵人實體、命中座標、造成的傷害與剩餘血量,後續的視覺特效都只需要讀取同一份資料即可。AttackPlugin 也同步註冊事件 (new_demo/src/plugins/attack.rs:8),確保效果插件能夠在同一個 Update 週期接到訊息。


打擊閃光與敵人受擊閃爍

new_demo/src/components/effects.rs:3 新增了三個特效元件,其中 HitSparkEnemyHitFlash 分別處理擊中閃光與受擊閃爍。系統放在 new_demo/src/systems/effects.rs

pub fn spawn_hit_spark_system(
    mut commands: Commands,
    mut events: EventReader<EnemyHitEvent>,
) {
    for event in events.read() {
        if event.damage <= 0 {
            continue;
        }

        commands.spawn((
            HitSpark::new(0.18, 1.8),
            Sprite {
                color: Color::srgba(1.0, 0.86, 0.48, 0.95),
                custom_size: Some(Vec2::new(18.0, 18.0)),
                ..Default::default()
            },
            Transform::from_translation(event.position + Vec3::Z * 20.0),
            Name::new("HitSpark"),
        ));
    }
}

update_hit_spark_system 使用 Timer 的進度來計算縮放與透明度 (new_demo/src/systems/effects.rs:31),讓閃光在 0.18 秒內逐漸擴散並淡出。敵人本體則由 apply_enemy_hit_flash_systemEnemyHitFlash 掛到實體上,update_enemy_hit_flash_system 每 0.04 秒切換一次顏色 (new_demo/src/systems/effects.rs:58),再由 Timer 決定何時還原成白色。實作上只需要操作 Sprite.color,不用顧慮 UV 或 shader。


死亡粒子 Timer + 隨機速度

所謂的死亡粒子(Death Particle Effect)就是當角色死亡時,畫面上散開的粒子效果,用來強化死亡的瞬間感與衝擊感

在擊殺敵人時,spawn_enemy_death_particles_system 會以事件的命中座標為中心,產生 9 個帶有速度向量的 DeathParticle (new_demo/src/systems/effects.rs:106):

let angle = rng.gen_range(0.0..TAU);
let speed = rng.gen_range(140.0..220.0);
let velocity = Vec2::new(angle.cos(), angle.sin()) * speed;

commands.spawn((
    DeathParticle::new(velocity, 0.6, Vec3::splat(scale_factor)),
    Sprite {
        color,
        custom_size: Some(Vec2::splat(10.0)),
        ..Default::default()
    },
    Transform::from_translation(event.position + Vec3::new(0.0, 0.0, 16.0)),
    Name::new("EnemyDeathParticle"),
));

這些粒子在 update_death_particles_system 內依照 Timer 的經過時間調整位置、縮放與透明度 (new_demo/src/systems/effects.rs:129)。每幀使用 delta_secs() 推進計算,保證粒子壽命與遊戲幀率無關。一旦 Timer 結束,就會自動 despawn。


CameraShake 資源,鏡頭震動的節拍器

鏡頭震動透過 CameraShake 資源管理 (new_demo/src/resources/camera_shake.rs:1)。這個資源紀錄 Timer、振幅、頻率與當前偏移量,並提供 trigger / update API:

impl CameraShake {
    pub fn trigger(&mut self, amplitude: f32, duration: f32) {
        self.timer = Timer::from_seconds(duration, TimerMode::Once);
        self.timer.reset();
        self.amplitude = amplitude;
        self.phase = 0.0;
        self.active = true;
    }

    pub fn update(&mut self, delta: f32) {
        if !self.active {
            self.offset = Vec2::ZERO;
            return;
        }

        self.timer.tick(Duration::from_secs_f32(delta));
        let progress = (self.timer.elapsed_secs() / self.timer.duration().as_secs_f32()).clamp(0.0, 1.0);
        let damping = 1.0 - progress;
        self.phase += delta * self.frequency;
        self.offset = Vec2::new(
            (self.phase * TAU).sin() * self.amplitude * damping,
            ((self.phase * TAU) + FRAC_PI_2).sin() * self.amplitude * damping * 0.6,
        );
    }
}

trigger_camera_shake_on_enemy_hit 根據事件判斷是擊中或擊殺 (new_demo/src/systems/effects.rs:148),分別給予 0.22 秒與 0.35 秒的震動。CameraPluginUpdate 中先執行 camera_follow_system,再呼叫 apply_camera_shake_system 把偏移量加到鏡頭位置 (new_demo/src/systems/camera.rs:24)。這樣可以保留原來的跟隨邏輯,又能在特效階段加上動態震動。


插件整合

所有打擊特效系統集中在新的 EffectsPlugin (new_demo/src/plugins/effects.rs:3),掛在 AttackPlugin 後方加入 App。EffectsPlugin 只需要宣告一次,未來要增加新的動畫也能放在同一個模組:

app.add_systems(
    Update,
    (
        spawn_hit_spark_system.after(player_melee_attack_system),
        update_hit_spark_system,
        apply_enemy_hit_flash_system.after(player_melee_attack_system),
        update_enemy_hit_flash_system,
        spawn_enemy_death_particles_system.after(player_melee_attack_system),
        update_death_particles_system,
        trigger_camera_shake_on_enemy_hit.after(player_melee_attack_system),
    ),
);

小結

demo

現在每次攻擊都會有閃爍的效果,而且畫面看起來會震動,最後如果角色死亡的話,會呈現成粒子四處散開
的效果,在打擊上呈現出良好的手感。

今日程式碼同步至 repo


上一篇
背景音樂與環境音系統
系列文
Bevy Rogue-lite 勇者冒險篇 × Rust 遊戲開發筆記28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言